Skip to Content

面试导航 - 程序员面试题库大全 | 前端后端面试真题 | 面试

JSX 是一种类似于 XML 的语法,它被设计为扩展 ECMAScript(即 JavaScript)。这种类 XML 语法让开发者可以在 JavaScript 代码中直接编写结构化的 HTML 元素。

由于 JSX 是一种声明式语法,实际用于构建抽象的视图层,本身并没有定义任何语义。这种抽象不会被引擎或浏览器直接执行,需要通过适配器(如 Babel 编译器和 React 渲染器)将其编译为标准的 ECMAScript,以适配各种显示终端。

在 React 生态系统中,渲染器 指的是将 React 组件渲染到特定环境中的工具或库。最常见的 React 渲染器是 React DOM,它将 React 组件渲染到 Web 浏览器的 DOM 中。此外,还有其他的 React 渲染器,例如 React Native 用于移动应用开发,React Three Fiber 用于 WebGL 场景渲染等。

JSX 将 HTML(JSX)、CSS 和 JS 包含在同一个文件中,与其说是单一责任原则,更不如说是关注点分离。React 组件的关注点是将一小部分信息渲染到屏幕上,而 HTML(JSX)、CSS 和 JS 都是实现这一目标所需要的部分,因此它们属于同一个关注点。

通过这种方式,可以显著提高组件的内聚性和松耦合度。将组件的所有相关代码(包括结构、样式和行为)放在一个文件中,使组件更加自包含和模块化。当需要修改组件时,开发者只需关注一个文件,减少了跨文件查找和修改的复杂度,从而提高了维护效率。

JSX 原理

本质上,JSX 只是 React.createElement(component, props, …children) 的语法糖。所有使用 JSX 语法书写的节点,都会被编译器转换,最终以 React.createElement(…) 的方式创建对应的 ReactElement 对象。

ReactElement 对象的数据结构如下:

export type Source = {| fileName: string, lineNumber: number, |}; export type ReactElement = {| $$typeof: any, type: any, key: any, ref: any, props: any, // ReactFiber _owner: any, // __DEV__ _store: {validated: boolean, ...}, _self: React$Element<any>, _shadowChildren: any, _source: Source, |};

首先,在上面的 Source 类型表示一个对象,包含与源码文件相关的信息。

  1. fileName: string:表示源码文件的文件名,类型是字符串。

  2. lineNumber: number:表示代码所在的行号,类型是数字。

ReactElement 类型表示一个 React 元素的结构,包含了创建 React 元素所需的所有信息。以下是各个属性的详细解释:

  1. $$typeof: any:一个特殊属性,用于标识这个对象是一个 React 元素。$$ typeof 通常是一个 Symbol 值,表示 React 元素的类型。

    其中 React 的元素的类型主要包括以下这些节点:

    // src/react/packages/shared/ReactSymbols.js export const REACT_ELEMENT_TYPE = Symbol.for('react.element'); export const REACT_PORTAL_TYPE = Symbol.for('react.portal'); export const REACT_FRAGMENT_TYPE = Symbol.for('react.fragment'); export const REACT_STRICT_MODE_TYPE = Symbol.for('react.strict_mode'); export const REACT_PROFILER_TYPE = Symbol.for('react.profiler'); export const REACT_PROVIDER_TYPE = Symbol.for('react.provider'); export const REACT_CONTEXT_TYPE = Symbol.for('react.context'); export const REACT_SERVER_CONTEXT_TYPE = Symbol.for('react.server_context'); export const REACT_FORWARD_REF_TYPE = Symbol.for('react.forward_ref'); export const REACT_SUSPENSE_TYPE = Symbol.for('react.suspense'); export const REACT_SUSPENSE_LIST_TYPE = Symbol.for('react.suspense_list'); export const REACT_MEMO_TYPE = Symbol.for('react.memo'); export const REACT_LAZY_TYPE = Symbol.for('react.lazy'); export const REACT_SCOPE_TYPE = Symbol.for('react.scope'); export const REACT_DEBUG_TRACING_MODE_TYPE = Symbol.for('react.debug_trace_mode'); export const REACT_OFFSCREEN_TYPE = Symbol.for('react.offscreen'); export const REACT_LEGACY_HIDDEN_TYPE = Symbol.for('react.legacy_hidden'); export const REACT_CACHE_TYPE = Symbol.for('react.cache'); export const REACT_TRACING_MARKER_TYPE = Symbol.for('react.tracing_marker'); export const REACT_SERVER_CONTEXT_DEFAULT_VALUE_NOT_LOADED = Symbol.for('react.default_value');

    这些 $$typeof 值是 React 内部实现的关键部分,通过它们,React 可以识别不同类型的 React 元素和组件,并进行相应的处理。

  2. type: any:表示 React 元素的类型,可以是一个字符串(如 ‘div’、‘span’),也可以是一个 React 组件(类组件或函数组件),在 reconciler 阶段, 会根据 type 执行不同的逻辑。

  3. key: any:React 元素的唯一标识符,用于高效地识别哪些元素发生了变化、被添加或被移除。

  4. ref: any:用于获取对 React 元素或组件实例的引用。

  5. props: any:包含传递给 React 元素的属性和子元素。

  6. _owner: any:ReactFiber 记录创建本对象的 Fiber 节点, 还未与 Fiber 树关联之前, 该属性为 null

  7. _store: {validated: boolean, ...}:内部属性,用于存储一些调试相关的信息。validated 属性是一个布尔值,表示该元素是否已经过验证。

  8. _self: React$Element<any>:指向当前元素自身的引用,通常用于调试目的,以便追踪元素的创建过程。

  9. _shadowChildren: any:内部属性,表示元素的子节点,通常用于调试和内部管理。

  10. _source: Source:指向 Source 类型,包含创建该元素的源码位置(文件名和行号),用于错误报告和调试。

如下图代码所示:

我们在前面中讲到的属性都能再控制台上一一显示出来:

React 为什么需要 $$typeof

React 使用 $$typeof 和 Symbol.for 来标识和区分不同类型的 React 元素。这些标识符用于确保 React 内部能够正确处理和渲染元素,并防止潜在的安全问题或冲突。

Symbol.for 使用全局符号注册表,这意味着在不同模块或作用域中使用相同的字符串调用 Symbol.for,都会返回相同的符号。

考虑一个恶意用户试图在你的应用中插入伪造的 React 元素。如果 React 仅仅依赖于普通字符串或简单的对象结构来标识 React 元素,攻击者可能会尝试创建类似结构的对象来欺骗 React。

const fakeElement = { type: 'div', props: { className: 'fake', children: 'Malicious content', }, key: null, ref: null, };

在上面的这些代码中,如果没有 $$typeofSymbol.for 的保护,React 可能会错误地处理这个伪造的元素,使用 $$typeof 和 Symbol.for 后,攻击者难以伪造有效的 React 元素,因为他们无法生成相同的全局唯一符号。

// src/react/packages/shared/isValidElementType.js export default function isValidElementType(type: mixed) { if (typeof type === "string" || typeof type === "function") { return true; } // Note: typeof might be other than 'symbol' or 'number' (e.g. if it's a polyfill). if ( type === REACT_FRAGMENT_TYPE || type === REACT_PROFILER_TYPE || (enableDebugTracing && type === REACT_DEBUG_TRACING_MODE_TYPE) || type === REACT_STRICT_MODE_TYPE || type === REACT_SUSPENSE_TYPE || type === REACT_SUSPENSE_LIST_TYPE || (enableLegacyHidden && type === REACT_LEGACY_HIDDEN_TYPE) || type === REACT_OFFSCREEN_TYPE || (enableScopeAPI && type === REACT_SCOPE_TYPE) || (enableCacheElement && type === REACT_CACHE_TYPE) || (enableTransitionTracing && type === REACT_TRACING_MARKER_TYPE) ) { return true; } if (typeof type === "object" && type !== null) { if ( type.$$typeof === REACT_LAZY_TYPE || type.$$typeof === REACT_MEMO_TYPE || type.$$typeof === REACT_PROVIDER_TYPE || type.$$typeof === REACT_CONTEXT_TYPE || type.$$typeof === REACT_FORWARD_REF_TYPE || // This needs to include all possible module reference object // types supported by any Flight configuration anywhere since // we don't know which Flight build this will end up being used // with. type.$$typeof === REACT_MODULE_REFERENCE || type.getModuleId !== undefined ) { return true; } } return false; }

isValidElementType 函数通过检查 type 的类型和特定的 $$typeof 属性,来验证传入的类型是否是一个有效的 React 元素类型。这有助于 React 内部处理和渲染元素,确保组件树的合法性和安全性。

通过这个函数来检测,如果我们用 Symbol 标记每个 React 元素,因为服务端的数据不会有 Symbol.for(‘react.element’),React 就可以检测 element.$$typeof,如果元素丢失或者无效,则可以拒绝处理该元素,这样就保证了安全性。

总结

每个 DOM 元素的结构都可以用 JavaScript 的对象来表示。你会发现一个 DOM 元素包含的信息其实只有三个:

  • 标签名 tagName

  • 属性 props

  • 子元素 children

<div class="box" id="content"> <div class="title">Hello</div> <button>Click</button> </div>

上述的 HTML 所有信息可以用合法的 JavaScript 对象来表示:

{ "tag": "div", "attrs": { "className": "box", "id": "content" }, "children": [ { "tag": "div", "attrs": { "className": "title" }, "children": ["Hello"] }, { "tag": "button", "attrs": null, "children": ["Click"] } ] }

如果我们可以使用 JavaScript 对象来描述所有能用 HTML 表示的 UI 信息,但如果描述整个页面内容,JavaScript 代码会变得非常冗长。

于是 React 就把 JavaScript 的语法扩展了一下,让 JavaScript 语言能够支持这种直接在 JavaScript 代码里面编写类似 HTML 标签结构的语法,这样写起来就方便很多了。编译的过程会把类 HTML 的 JSX 结构转换成 JavaScript 的对象结构。

React.createElement 会构建一个 JavaScript 对象来描述你 HTML 结构的信息,包括标签名、属性、还有子元素等。

在这个流程图中,React 构造是指 React 库中的 React.createElement 方法。这个方法负责将 JSX 转换为 JavaScript 对象结构。

完整流程如下:

  1. JSX:我们编写的 react 代码。

  2. Babel 编译 + React 构造:Babel 会将 JSX 代码编译成 React.createElement 调用。React.js 通过这些调用来构造 JavaScript 对象。

// JSX const element = <div className="box">Hello World</div>; // Babel 编译 + React 构造 const element = React.createElement('div', { className: 'box' }, 'Hello World');
  1. JavaScript 对象结构:这一步中,JSX 被转换成了 JavaScript 对象,这些对象描述了 UI 的结构。

  2. ReactDOM.render:这个方法将 JavaScript 对象转换为实际的 DOM 元素,并插入到页面中。

ReactDOM.render(element, document.getElementById('root'));

最终完成了一整个项目的渲染。

最后更新于:
Copyright © 2025Moment版权所有粤ICP备2025376666